package cloudspace.vm.javassist;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import javassist.CtMethod;
import javassist.CtNewMethod;
import javassist.NotFoundException;

import org.apache.log4j.Logger;


/**
 * The Class ConfigParser. This class is responsible for parsing configurations.
 * The config parser will parse a configuration file once. this config parser
 * should be kept as a wrapper for the configuration file. The parser will
 * automatically update if the underlying configurationfile has been updated.
 */
public class ConfigParser
{

    /** The time stamp. */
    private long timeStamp;

    /** The config file. */
    private File configFile;

    /** The expr editors. */
    private List<CommandInterface> exprEditors;

    private Map<String, CtClass> customClasses;

    /** The start command. */
    Pattern startCommand;

    private ClassPool proxyPool;

    private Logger logger = Logger.getLogger( ConfigParser.class );


    /**
     * Instantiates a new config parser.
     * 
     * @param file
     *            the file to use as a backend configuration.
     * 
     * @throws IOException
     *             Signals that an I/O exception has occurred.
     */
    public ConfigParser( File file, ClassPool pPool ) throws IOException
    {
        proxyPool = pPool;
        customClasses = new HashMap<String, CtClass>();
        startCommand = Pattern.compile( "replace|insertAfter|insertBefore|define" );
        configFile = file;
        timeStamp = configFile.lastModified();
        if ( timeStamp == 0 )
        {
            throw new FileNotFoundException();
        }
        parseEditors();
    }


    /**
     * This will look for updates to the configuration file and automatically
     * update the editors underneath
     * 
     * @throws IOException
     *             Signals that an I/O exception has occurred.
     */
    public void autoUpdate() throws IOException
    {
        if ( timeStamp != configFile.lastModified() )
        {
            parseEditors();
            timeStamp = configFile.lastModified();
        }
    }


    private String grepUntillEOC( Scanner confScanner, String EOC )
    {
        Pattern endOfCommand = Pattern.compile( EOC );
        String fullCommand = "";
        while ( confScanner.hasNext() && !confScanner.hasNext( endOfCommand ) )
        {
            String commandPart = confScanner.next();
            fullCommand += " ";
            fullCommand += commandPart;
        }
        confScanner.next();
        return fullCommand;
    }


    private void parseEditors() throws IOException
    {

        Pattern comment = Pattern.compile( "^//.*" );
        Pattern translationType = Pattern.compile( "translate" );
        Pattern macroType = Pattern.compile( "define" );
        Pattern proxyType = Pattern.compile( "proxy" );
        exprEditors = new ArrayList<CommandInterface>();
        FileReader inputStream = new FileReader( configFile );
        Scanner configScanner = new Scanner( inputStream );
        while ( configScanner.hasNext() )
        {

            if ( configScanner.hasNext( comment ) )
            {
                configScanner.nextLine();
                continue;
            }
            else if ( configScanner.hasNext( translationType ) )
            {
                configScanner.next();
                createCommand( configScanner );
            }
            else if ( configScanner.hasNext( macroType ) )
            {
                try
                {
                    MacroDefinition curMacro = createMacro( configScanner );
                    CommandFactory.registerMacro( curMacro );
                }
                catch ( MalformedMacroException e )
                {
                    logger.error( "Bad Macro", e );
                }

            }
            else if ( configScanner.hasNext( proxyType ) )
            {
                createProxy( configScanner );
            }
            else
            {
                configScanner.next();
            }

        }
    }


    private void createProxy( Scanner configScanner )
    {
        configScanner.next();
        String proxyClass = configScanner.next();
        int lastAccessor = proxyClass.lastIndexOf( '.' );
        String newClassName = "auto.generated._"
            + proxyClass.substring( lastAccessor + 1 );
        CtClass pClass = proxyPool.makeClass( newClassName );
        try
        {

            CtClass actualClass = proxyPool.get( proxyClass );
            CtMethod[] methods = actualClass.getMethods();
            CtConstructor[] constructors = actualClass.getConstructors();
            for ( CtConstructor constr : constructors )
            {
                if ( Modifier.isPublic( constr.getModifiers() ) )
                {
                    CtClass[] paramTypes = constr.getParameterTypes();
                    String methodSig = "public static " + actualClass.getName()
                        + " _constructor(";
                    int argNum = 0;
                    for ( CtClass param : paramTypes )
                    {
                        methodSig += param.getName();
                        methodSig += " arg" + argNum;
                        methodSig += ",";
                        argNum++;
                    }
                    if ( paramTypes.length != 0 )
                        methodSig = methodSig.substring( 0,
                            methodSig.length() - 1 );
                    methodSig += ")";
                    CtClass[] execeptionTypes = constr.getExceptionTypes();
                    if ( execeptionTypes.length != 0 )
                    {
                        methodSig += " throws ";
                    }

                    for ( CtClass exception : execeptionTypes )
                    {
                        methodSig += exception.getName() + ",";
                    }
                    if ( execeptionTypes.length != 0 )
                        methodSig = methodSig.substring( 0,
                            methodSig.length() - 1 );
                    methodSig += "{return new " + actualClass.getName() + "(";
                    for ( int i = 0; i < paramTypes.length; i++ )
                    {
                        methodSig += "arg" + i;
                        if ( i < paramTypes.length - 1 )
                            methodSig += ",";

                    }
                    methodSig += ");}";
                    try
                    {
                        CtMethod proxyMethod = CtNewMethod.make( methodSig,
                            pClass );
                        pClass.addMethod( proxyMethod );
                    }
                    catch ( CannotCompileException e )
                    {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }

                }
            }
            for ( CtMethod method : methods )
            {
                if ( Modifier.isPublic( method.getModifiers() ) )
                {
                    String methodName = method.getName();
                    String returnType = method.getReturnType().getName();
                    CtClass[] paramTypes = method.getParameterTypes();
                    String methodSig = "public static " + returnType + " _"
                        + methodName + "(";
                    int argNum = 0;
                    if ( !Modifier.isStatic( method.getModifiers() ) )
                    {
                        methodSig += proxyClass + " arg0";
                        if ( paramTypes.length != 0 )
                        {
                            methodSig += ",";
                        }
                        argNum++;
                    }
                    for ( int i = 0; i < paramTypes.length; i++ )
                    {
                        methodSig += paramTypes[i].getName();
                        methodSig += " arg" + argNum;
                        argNum++;
                        if ( i + 1 < paramTypes.length )
                        {
                            methodSig += ",";
                        }
                    }
                    methodSig += ")";
                    CtClass[] execeptionTypes = method.getExceptionTypes();
                    if ( execeptionTypes.length != 0 )
                    {
                        methodSig += " throws ";
                    }

                    for ( CtClass exception : execeptionTypes )
                    {
                        methodSig += exception.getName() + ",";
                    }
                    if ( execeptionTypes.length != 0 )
                        methodSig = methodSig.substring( 0,
                            methodSig.length() - 1 );
                    methodSig += "{";
                    if ( !returnType.equals( "void" ) )
                        methodSig += "return ";
                    argNum = 0;
                    if ( Modifier.isStatic( method.getModifiers() ) )
                    {
                        methodSig += actualClass.getName() + "." + methodName
                            + "(";
                    }
                    else
                    {
                        methodSig += "arg" + argNum + "." + methodName + "(";
                        argNum++;
                    }
                    for ( int i = 0; i < paramTypes.length; i++ )
                    {
                        methodSig += "arg" + argNum;
                        if ( i + 1 < paramTypes.length )
                            methodSig += ",";
                        argNum++;
                    }
                    methodSig += ");}";
                    try
                    {
                        CtMethod proxyMethod = CtNewMethod.make( methodSig,
                            pClass );
                        pClass.addMethod( proxyMethod );
                    }
                    catch ( CannotCompileException e )
                    {
                        logger.error( "Cannot Compile the new method for:  "
                            + method.getName(), e );
                    }

                }
            }
        }
        catch ( NotFoundException e )
        {
            logger.error( "Could not find class for proxy", e );
        }
        customClasses.put( proxyClass, pClass );
    }


    private MacroDefinition createMacro( Scanner configScanner )
        throws MalformedMacroException
    {
        configScanner.next();
        String macro = configScanner.nextLine().trim();
        Pattern quotePat = Pattern.compile( "\\<\\<\\s+[a-zA-Z0-9]+" );
        String quotation = null;
        Matcher quoteMatch = quotePat.matcher( macro );
        if ( quoteMatch.find() )
        {
            quotation = quoteMatch.group();

        }
        if ( quotation != null )
        {
            String commandPart = macro.substring( macro.indexOf( quotation )
                + quotation.length() ).trim();
            macro = macro.substring( 0, macro.indexOf( quotation ) ).trim()
                + " \"";
            quotation = quotation.substring( 2 ).trim();
            // Pattern endOfCommandPattern = Pattern.compile(quotation);

            if ( commandPart.contains( quotation ) )
            {
                commandPart = commandPart.substring( 0,
                    commandPart.indexOf( quotation ) );
            }
            else
            {
                commandPart += grepUntillEOC( configScanner, quotation ) + "\"";
                /*
                 * while(!configScanner.hasNext(endOfCommandPattern)) {
                 * commandPart += configScanner.next(); } configScanner.next();
                 * }
                 */
                macro += " " + commandPart;
            }
        }

        MacroDefinition curMacro = new MacroDefinition( macro );
        return curMacro;
    }


    private void createCommand( Scanner configScanner )
    {
        Pattern beginOfCommand = Pattern.compile( "<<|\\\".*" );
        List<String> args = new ArrayList<String>();
        boolean end = false;
        while ( configScanner.hasNext() && !end )
        {
            while ( !configScanner.hasNext( beginOfCommand ) )
            {
                args.add( configScanner.next() );
            }

            if ( configScanner.hasNext( beginOfCommand ) )
            {
                if ( configScanner.hasNext( "\\\".*" ) )
                {
                    String commandSection = configScanner.nextLine();
                    commandSection = commandSection.trim();
                    args.add( commandSection );
                    end = true;
                }
                else if ( configScanner.hasNext( "<<" ) )
                {
                    configScanner.next();
                    String commandEnd = configScanner.next();
                    String fullCommand = "\""
                        + grepUntillEOC( configScanner, commandEnd ) + "\"";
                    args.add( fullCommand );
                    end = true;
                }
            }
            else
            {
                String fullCommand = configScanner.nextLine();
                args.add( fullCommand );
                end = true;
            }

        }
        try
        {
            CommandInterface curCommand = CommandFactory.createCommand( args );
            exprEditors.add( curCommand );
        }
        catch ( MalformedCommandException e )
        {
            logger.error( "Bad command:  " + args, e );
        }
        catch ( MalformedMacroException e )
        {
            logger.error( "Bad Macro:  " + args, e );
        }

    }


    /**
     * Gets all of the commands located in a configuration file.
     * 
     * @return the commands parsed from the configuration file.
     */
    public List<CommandInterface> getCommands()
    {
        try
        {
            autoUpdate();
        }
        catch ( IOException e )
        {
            logger.error( "Config file is no longer valid.  For example, it might be deleted",
                e );
        }
        return exprEditors;
    }


    public CtClass getProxy( String className )
    {
        return customClasses.get( className );

    }

}
